package algorithme;

import model.Arc;
import model.Graph;
import model.Noeud;
import test.Application;
import test.Config;
import view.GUIHelper;

import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import java.io.File;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 * User: Thomas
 * Date: 04/02/14
 * Time: 10:48
 */
public abstract class AlgorithmePlusCourtChemin {

    /**
     * Liste chaînée permettant la gestion de la recherche du plus court chemin
     * Fonctionnement à l'envers avec des previous et non des next
     */
    protected class LinkedListNoeudPath implements Comparable<LinkedListNoeudPath> {
        //Le noeud actuel
        private Noeud value;
        //La distance pour arrivé à ce noeud
        private double distance;
        //Le noeud précedent
        private LinkedListNoeudPath previous;
        //L'arc pour aller au noeud précédent
        private Arc arc;
        //Le nombre de correspondance pour atteindre ce noeud
        private int nbCorrespondance = 0;

        public LinkedListNoeudPath(Noeud value, double distance) {
            this.value = value;
            this.distance = distance;
        }

        /**
         * Méthode permettant la comparaison avec un autre LinkedListNoeudPath en fonction de leur distance
         * @param l le noeud à comparer
         * @return 1 si this > l, 0 si this == l, -1 sinon
         */
        public int compareTo(LinkedListNoeudPath l){
            return (int) (this.getDistance() - l.getDistance());
        }

        public int getNbCorrespondance() {
            return nbCorrespondance;
        }

        public void setNbCorrespondance(int nbCorrespondance) {
            this.nbCorrespondance = nbCorrespondance;
        }

        Arc getArc() {
            return arc;
        }

        public void setArc(Arc arc) {
            this.arc = arc;
        }

        public double getDistance() {
            return distance;
        }

        public void setDistance(double distance) {
            this.distance = distance;
        }

        Noeud getValue() {
            return value;
        }

        void setValue(Noeud value) {
            this.value = value;
        }

        LinkedListNoeudPath getPrevious() {
            return previous;
        }

        public void setPrevious(LinkedListNoeudPath previous) {
            this.previous = previous;
        }
    }

    /**
     * Méthode permettant l'execution de l'algorithme en récoltant les statistiques et en affichant la solution
     * @param graph le graph dans lequel se situe la recherche
     * @param source le noeud de départ
     * @param destination le noeud de destination
     */
    public void execute(Graph graph, Noeud source, Noeud destination) {
        long time_start = System.currentTimeMillis();

        //execution de l'algorithme avec récupération du dernier noeud de la liste (noeud de destination)
        LinkedListNoeudPath last = this.plusCourtChemin(graph, source, destination);

        long time_end = System.currentTimeMillis();
        //gestion des statistiques (time)
        String stats = this.getClass().getSimpleName() + "\n Temps d'execution: " + (time_end - time_start) + "ms\n";
        if (last != null) {
            stats += " Cout du chemin : " + last.getDistance()+"\n";
        } else {
            stats += " No path found\n";
        }
        //ajout des statistiques à l'UI (toolbar)
        GUIHelper.add2History(stats);

        //affichage du plus court chemin graphiquement
        this.playPath(last);
    }


    /**
     * Recherche le plus court chemin
     * @param graph le graph dans lequel se situe la recherche
     * @param source le noeud de départ
     * @param destination le noeud de destination
     * @return la liste chainée de destination vers la source
     */
    protected abstract LinkedListNoeudPath plusCourtChemin(Graph graph, Noeud source, Noeud destination);

    protected HashMap<Integer, LinkedListNoeudPath> initTableauDistance(Graph graph, Noeud source) {

        HashMap<Integer, LinkedListNoeudPath> distances = new HashMap<Integer, LinkedListNoeudPath>();

        for(Map.Entry<Integer, Noeud> entry : graph.getNoeuds().entrySet()) {
            Noeud noeud = entry.getValue();
            distances.put(noeud.getId(),new LinkedListNoeudPath(noeud,Double.POSITIVE_INFINITY));
        }

        distances.remove(source.getId());
        distances.put(source.getId(),new LinkedListNoeudPath(source,0.0));

        return distances;
    }

    /**
     * Méthode permettant l'affichage graphique du chemin et des animations
     * @param last le dernier point de la liste chaînée afin de la remonter
     */
    public void playPath(final LinkedListNoeudPath last) {
        //l'affichage se fait dans un thread afin de pouvoir gérer des animations
        new Thread(new Runnable() {
            @Override
            public void run() {
                //Récupération du dernier noeud de la liste ( noeud de destination)
                LinkedListNoeudPath last_bis = last;
                //Création d'une liste permettant de remettre la précédente liste dans le bon sens
                LinkedList<LinkedListNoeudPath> stack_arcs = new LinkedList<LinkedListNoeudPath>();

                while (last_bis != null) {
                    //on empile les LinkedListNoeudPath pour les mettre dans le sens inverse
                    stack_arcs.addFirst(last_bis);
                    System.out.print(last_bis.getValue().getName());

                    //si l'on est pas encore à la fin
                    if (last_bis.getArc() != null) {
                        //Selection de l'arc pour signifier qu'il fait parti du plus court chemin
                        last_bis.getArc().getArcDrawable().setSelected(true);
                        System.out.print(" : " + last_bis.getArc().getLigne() + " cout: " + last_bis.getArc().getCout() + " -> ");
                    }
                    //boucle sur la liste chaînée
                    last_bis = last_bis.getPrevious();
                }
                //play animation
                LinkedListNoeudPath current;
                //tant qu'il y a des noeuds dans la pile
                while (stack_arcs.size() > 0) {
                    current = stack_arcs.getFirst();
                    stack_arcs.pop();

                    if (current.getArc() != null) {
                        //gestion de l'animation du tram
                        int i = 0;
                        //pour chaque point de l'arc on déplace le tram avec un sleep pour une impression de vitesse
                        while (i <= current.getArc().getArcDrawable().getNb_points_arc()) {
                            try {
                                //gestion de la vitesse du tram
                                Thread.sleep((long) ((Config.VITESSE_TRAM * current.getArc().getCout()) / current.getArc().getArcDrawable().getNb_points_arc()));
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            current.getArc().getArcDrawable().setPosition_draw_tram(i);
                            //réaffichage du canvas
                            Application.getWindowS().getjCanvas().repaint();
                            i++;
                        }
                        current.getArc().getArcDrawable().setPosition_draw_tram(-1);
                    }
                }
                //jouer un son en fin d'affichage
                try {
                    Clip clip = AudioSystem.getClip();
                    clip.open(AudioSystem.getAudioInputStream(new File("./ressources/tram_cloche.wav")));
                    clip.start();
                }
                catch (Exception exc) {
                    exc.printStackTrace(System.out);
                }
            }
        }).start();
    }
}
